package net.sf.cldsql.dj.db;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.sf.cldsql.dj.utl.DiException;


/**
 * ConnPool
 */
public class ConnPool {
    
    final Logger log = LoggerFactory.getLogger(ConnPool.class);
    
    private String name;
    private String dbms;
    private String driver;
    private String url;
    private String username;
    private String password;
    private int maxConn;
    
    private final ArrayList<DbConnection> idleConns = new ArrayList<DbConnection>();
    private final ArrayList<DbConnection> usingConns = new ArrayList<DbConnection>();

    private String rsetNamingStyle;

    private boolean autoCommit;

    private int maxRetries;

    /** @return name */
    public String getName() {
        return name;
    }

    /** @return dbms */
    public String getDbms() {
        return dbms;
    }

    /** @return rsetNamingStyle */
    String getRsetNamingStyle() {
        return rsetNamingStyle;
    }

    public ConnPool (String name, String dbms, String driver, String url, String username, String password, int maxConn, String rsetNamingStyle, int maxRetries, boolean autoCommit) {
        log.info("ConnPool {} start with {} dbms {} to {} for {} has maxConn {} with rset naming style {}, max retries {}, auto commit {}.", name, dbms, driver, url, username, maxConn, rsetNamingStyle, maxRetries, autoCommit);
        
        this.name = name;
        this.dbms = dbms;
        this.driver = driver;
        this.url = url;
        this.username = username;
        this.password = password;
        this.maxConn = maxConn;
        this.rsetNamingStyle = rsetNamingStyle;
        this.maxRetries = maxRetries;
        this.autoCommit = autoCommit;
        startup();
        log.info("ConnPool {} initialized.", name);
    }
    
    public void startup() {
        try {
            Class.forName(driver);
            
            // add to pool under sync mode
            synchronized (this) {
                for (int i = 0; i < maxConn; i ++) {
                    log.debug("Create conn #{}.", i);
                    Connection conn = DriverManager.getConnection(
                            url,
                            username, 
                            password);
                    log.debug("Created conn #{} {}.", i, conn);
                    conn.setAutoCommit(autoCommit);
                    idleConns.add(new DbConnection(conn, this));
                    //no usingConns
                }
            }
        } catch (SQLException e) {
            throw new DiException(e);
        } catch (ClassNotFoundException e) {
            throw new DiException(e);
        }
    }
    
    synchronized public void shutdown() {
        log.info("ConnPool {} shutdown start, disconnect idle {}, using {}.", name, idleConns.size(), usingConns.size());
        for (int i = 0; i < idleConns.size(); i ++) {
            DbConnection conn = idleConns.get(i);
            try {
                conn.getConn().close();
                idleConns.remove(i);
            } catch (SQLException e) {
                throw new DiException(e);
            }
        }
        for (int i = 0; i < usingConns.size(); i ++) {
            DbConnection conn = usingConns.get(i);
            try {
                conn.getConn().close();
                usingConns.remove(i);
            } catch (SQLException e) {
                throw new DiException(e);
            }
        }
        log.info("ConnPool {} shutdown finished.", name);
    }
    
    /**
     * @return connection
     */
    public DbConnection get() {
        // TODO timeout
        int i;
        for (i = 0; maxRetries == 0 || i < this.maxRetries; i ++) {
            if (idleConns.size() == 0) {
                try {
                    log.debug("ConnPool {} start wait for no more idle conn, retry {} times.", name, i);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new DiException(e);
                }
            } else {
                // remove from idle pool under sync mode
                synchronized (this) {
                    if (idleConns.size() != 0) {
                        DbConnection conn = idleConns.remove(idleConns.size() - 1);
                        usingConns.add(conn);
                        log.debug("ConnPool {} got conn {} status idle {} using {}.", name, conn, idleConns.size(), usingConns.size());
                        return conn;
                    }
                }
            }
        }
        log.error("ConnPool {} no more idle conn after retry {} times.", name, i);
        throw new DiException("no more idle conn after retry " + i + " times.");
    }
    
    /**
     * @param conn
     */
    public void release (DbConnection conn) {
        // release and add to pool
        synchronized (this) {
            idleConns.add(conn);
            usingConns.remove(conn);
            log.debug("ConnPool {} released conn {} status idle {} using {}.", name, conn, idleConns.size(), usingConns.size());
        }
    }
    
    protected ArrayList<DbConnection> getIdleConns() {
        return idleConns;
    }
    
    protected ArrayList<DbConnection> getUsingConns() {
        return usingConns;
    }
    
    public void finalize () {
        //shutdown ();
    }

    /** @return autoCommit */
    public boolean isAutoCommit() {
        return autoCommit;
    }
}
